package hu.mapro.model.generator;

import hu.mapro.model.generator.AutoBeanGenerator.DefinedTypeInfo;
import hu.mapro.model.generator.AutoBeanGenerator.ExtraInfo;
import hu.mapro.model.impl.Cardinality;
import hu.mapro.model.meta.BuiltinType;
import hu.mapro.model.meta.ComplexType;
import hu.mapro.model.meta.DefinedType;
import hu.mapro.model.meta.EntityType;
import hu.mapro.model.meta.EnumType;
import hu.mapro.model.meta.Field;
import hu.mapro.model.meta.MetaUtils;
import hu.mapro.model.meta.PluralField;
import hu.mapro.model.meta.SingularField;
import hu.mapro.model.meta.Type;
import hu.mapro.model.meta.ValueType;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

@SuppressWarnings("rawtypes")
public class MetaFactory<T> {


	public static interface FieldInfo<T> {

		String getName();
		String getReadMethod();
		String getWriteMethod();
		boolean isList();
		boolean isSet();
		String getInverseFieldName();
		T getValueType();
		
	}
	
	public static interface TypeInfo<T> {
		
		boolean isEnumeration();
		boolean isEntity();
		boolean isAbstract();
		String getFullName();
		T getSuperType();
		Collection<FieldInfo<T>> getFields();
		ClassCustomizer getCustomizer();
		
	}
	
	Collection<T> types;
	Function<T, TypeInfo<T>> info;
	
	public MetaFactory(Collection<T> types, Function<T, TypeInfo<T>> info) {
		super();
		this.types = types;
		this.info = info;
	}


	Set<String> domainTypes;
	
	//Map<String, Type> typeMap = Maps.newHashMap();
	
	Type getType(T type) {
//		TypeInfo<T> typeInfo = info.apply(type);
//		Type t = typeMap.get(typeInfo.getFullName());
//		
//		if (t==null) {
//			t = createType(type);
//			typeMap.put(typeInfo.getFullName(), t);
//		}
//		
//		return t;
		
		return createType(type);
	}
	
	private Type createType(T type) {
		TypeInfo<T> typeInfo = info.apply(type);

		TypeBase t = null;
		
		if (MetaUtils.isBuiltinType(typeInfo.getFullName())) {
			t = new BuiltinTypeBase();
		} else if (typeInfo.isEnumeration()) {
			t = new EnumTypeBase();
		} else if (typeInfo.isEntity()) {
			t = new EntityTypeBase();
		} else {
			t = new ValueTypeBase();
		}

		t.typeInfo = typeInfo;
		
		return t;
	}
	
	private Field createField(FieldInfo<T> fieldInfo) {
		FieldBase field;
		
		if (fieldInfo.isList()||fieldInfo.isSet()) {
			field = new PluralFieldBase();
		} else {
			field = new SingularFieldBase();
		}
		
		field.fieldInfo = fieldInfo;
		
		return field;
	}

	public Collection<DefinedType<?>> generate() {
		ArrayList<DefinedType<?>> generatedTypes = Lists.newArrayList();
		
		
		for (T type : types) {
			generatedTypes.add((DefinedType) getType(type));
		}
		
		return generatedTypes;
	}
	
	
	@SuppressWarnings("unchecked")
	public final ExtraInfo extraInfo = new ExtraInfo() {
		
		@Override
		public hu.mapro.model.generator.AutoBeanGenerator.FieldInfo getInfo(final Field field) {
			return new hu.mapro.model.generator.AutoBeanGenerator.FieldInfo() {
				
				@Override
				public String getWriteMethod() {
					return ((FieldBase)field).fieldInfo.getWriteMethod();
				}
				
				@Override
				public String getReadMethod() {
					return ((FieldBase)field).fieldInfo.getReadMethod();
				}
			};
		}
		
		@Override
		public DefinedTypeInfo getInfo(DefinedType type) {
			return new DefinedTypeInfoBase(type);
		}
		
		@Override
		public hu.mapro.model.generator.AutoBeanGenerator.TypeInfo getInfo(final Type type) {
			return new TypeInfoBase(type);
		}
	};
	
	class TypeBase implements Type {

		TypeInfo<T> typeInfo;
		
		@Override
		public TypeCategory getTypeCategory() {
			if (MetaUtils.isBuiltinType(typeInfo.getFullName())) return TypeCategory.BUILTIN;
			if (typeInfo.isEnumeration()) return TypeCategory.ENUMERATION;
			if (typeInfo.isEntity()) return TypeCategory.ENTITY;
			return TypeCategory.VALUE;
		}

	}
	
	class BuiltinTypeBase extends TypeBase implements BuiltinType {
		
		@Override
		public BuiltinTypeCategory getBuiltinTypeCategory() {
			return MetaUtils.getBuiltinType(typeInfo.getFullName());
		}
		
	}
	
	class DefinedTypeBase extends TypeBase implements DefinedType {
		
	}
	
	class ComplexTypeBase extends DefinedTypeBase implements ComplexType {

		@Override
		public ComplexType getSuperType() {
			T st = typeInfo.getSuperType();
			if (st==null) {
				return null;
			}
			return (ComplexType) getType(st);
		}

		@Override
		public Collection<Field> getFields() {
			return Collections2.transform(typeInfo.getFields(), new Function<FieldInfo<T>, Field>() {
				@Override
				public Field apply(FieldInfo<T> input) {
					return createField(input);
				}
			});
		}
		
		@Override
		public boolean isAbstract() {
			return typeInfo.isAbstract();
		}
		
	}
	
	class EntityTypeBase extends ComplexTypeBase implements EntityType {
		
	}
	
	class ValueTypeBase extends ComplexTypeBase implements ValueType {
		
	}
	
	class EnumTypeBase extends DefinedTypeBase implements EnumType {
		
	}
	
	
	abstract class FieldBase implements Field {
		
//		T ownerType;
		FieldInfo<T> fieldInfo;

//		@Override
//		public ComplexType getOwnerType() {
//			return (ComplexType) getType(ownerType);
//		}

		@Override
		public String getName() {
			return fieldInfo.getName();
		}

		@Override
		public boolean isReadable() {
			return fieldInfo.getReadMethod()!=null;
		}

		@Override
		public boolean isWritable() {
			return fieldInfo.getWriteMethod()!=null;
		}

		@Override
		public Type getValueType() {
			return getType(fieldInfo.getValueType());
		}
		
		@Override
		public Cardinality getCardinality() {
			if (fieldInfo.isList()) return Cardinality.LIST;
			if (fieldInfo.isSet()) return Cardinality.SET;
			return Cardinality.SCALAR;
		}
		
	}
	
	
	class PluralFieldBase extends FieldBase implements PluralField {

		@SuppressWarnings("unchecked")
		@Override
		public SingularField getInverseField() {
			if (fieldInfo.getInverseFieldName()==null) return null;
			
			return (SingularField) MetaUtils.findField((ComplexType) getType(fieldInfo.getValueType()), fieldInfo.getInverseFieldName());
		}
		
	}
	class SingularFieldBase extends FieldBase implements SingularField {
		
	}

	class TypeInfoBase implements
			hu.mapro.model.generator.AutoBeanGenerator.TypeInfo {

		Type type;

		TypeInfoBase(Type type) {
			this.type = type;
		}

		@SuppressWarnings("unchecked")
		@Override
		public String getClassFullName() {
			return ((TypeBase) type).typeInfo.getFullName();
		}

		@SuppressWarnings("unchecked")
		@Override
		public ComplexType getSuperClass() {
			T superType = ((TypeBase) type).typeInfo
					.getSuperType();
			if (superType==null) return null;
			return (ComplexType) getType(superType);
		}
	}

	class DefinedTypeInfoBase extends TypeInfoBase implements DefinedTypeInfo {

		DefinedTypeInfoBase(Type type) {
			super(type);
		}

		@SuppressWarnings("unchecked")
		@Override
		public ClassCustomizer getCustomizer() {
			return ((TypeBase) type).typeInfo.getCustomizer();
		}
	}

}
